package ee.fj.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * All purpose of reflection helper. Searches private methods and fields.
 * Key fields should be dot-separated. If first null value met returns null.
 * java.util.Map and java.util.List supported. Just use as dot-separated fields.
 * @author Margus Rebane
*/
public class ReflectionHelper {
	private static Field getField(@SuppressWarnings("rawtypes") Class c, String key) throws SecurityException, NoSuchFieldException {
		try {
			return c.getField(key);
		} catch (Exception e) {
			return c.getDeclaredField(key);
		}
	}

	@SuppressWarnings("unchecked")
	private static Method getMethod(@SuppressWarnings("rawtypes") Class c, String key, Class... parameterTypes) throws SecurityException, NoSuchMethodException{
		try {
			return c.getMethod(key, parameterTypes);
		} catch (Exception e) {
			return c.getDeclaredMethod(key, parameterTypes);
		}
		
	}

	public static Object getValue(Object obj, String key) throws NoSuchFieldException {
		String[] s = key.split("\\.");
		if (s.length == 0) {
			return getValueInner(obj, key);
		}
		Object retVal = obj;
		for (String v : s) {
			retVal = getValueInner(retVal, v);
		}
		return retVal;
	}

	public static void setValue(Object obj, String key, Object value) throws NoSuchFieldException {
		setValueInner(obj, key, value);
	}

	public static void setValue(Field field, Object obj, Object value) throws NumberFormatException, IllegalArgumentException, IllegalAccessException {
		Class type = field.getType();
		if (type.equals(Long.class) && !(value instanceof Long)) {
			field.set(obj, Long.parseLong(value.toString()));
		} else if (type.equals(Integer.class) && !(value instanceof Integer)) {
			field.set(obj, Integer.parseInt(value.toString()));
		} else if (type.equals(Float.class) && !(value instanceof Float)) {
			field.set(obj, Float.parseFloat(value.toString()));
		} else if (type.equals(Double.class) && !(value instanceof Double)) {
			field.set(obj, Double.parseDouble(value.toString()));
		} else {
			field.set(obj, value);
		}
	}

	@SuppressWarnings("rawtypes")
	private static void setValueInner(Object obj, String key, Object value) throws NoSuchFieldException {
		Class c = obj.getClass();
		Field field = null;
		try {
			field = getField(c, key);
			setValue(field, obj, value);
			return;
		} catch (Exception e) {
		}

		Method m1 = null;
		try {
			m1 = getMethod(c, key, value.getClass());
			m1.invoke(obj, value);
			return;
		} catch (Exception e1) {
		}

		Method m2 = null;
		String u_key = key.substring(0, 1).toUpperCase() + key.substring(1);
		try {
			m2 = getMethod(c, "set" + u_key, value.getClass());
			m2.invoke(obj, value);
			return;
		} catch (Exception e) { }

		try {
			if (m2 != null && !m2.isAccessible()) {
				m2.setAccessible(true);
				m2.invoke(obj, value);
				return;
			}
		} catch (Exception e) {}
		try {
			if (m1 != null && !m1.isAccessible()) {
				m1.setAccessible(true);
				m1.invoke(obj, value);
				return;
			}
		} catch (Exception e) {}
		try {
			if (field != null && !field.isAccessible()) {
				field.setAccessible(true);
				setValue(field, obj, value);
				return;
			}
		} catch (Exception e) {}

		
		if (obj instanceof Map) {
			if (((Map)obj).containsKey(key)) {
				((Map)obj).put(key, value);
				return;
			}
		} else {

		}
		throw new NoSuchFieldException("Class " + c.getName() + " does not have field " + key + " or methods " + key + "(), set" + u_key + "()!");
	}
	
	@SuppressWarnings("rawtypes")
	private static Object getValueInner(Object obj, String key) throws NoSuchFieldException {
		Class c = obj.getClass();
		Field field = null;
		try {
			field = getField(c, key);
			return field.get(obj);
		} catch (Exception e) {
		}

		Method m1 = null;
		try {
			m1 = getMethod(c, key);
			return m1.invoke(obj);
		} catch (Exception e1) {
		}

		Method m2 = null;
		String u_key = key.substring(0, 1).toUpperCase() + key.substring(1);
		try {
			m2 = getMethod(c, "get" + u_key);
			return m2.invoke(obj);
		} catch (Exception e) {}

		try {
			if (m2 != null && !m2.isAccessible()) {
				m2.setAccessible(true);
				return m2.invoke(obj);
			}
		} catch (Exception e) {}
		try {
			if (m1 != null && !m1.isAccessible()) {
				m1.setAccessible(true);
				return m1.invoke(obj);
			}
		} catch (Exception e) {}
		try {
			if (field != null && !field.isAccessible()) {
				field.setAccessible(true);
				return field.get(obj);
			}
		} catch (Exception e) {}

		
		if (obj instanceof Map) {
			if (((Map)obj).containsKey(key)) {
				return ((Map)obj).get(key);
			} else {
				return null;
			}
		} else if (obj instanceof List) {
			try {
				int index = Integer.parseInt(key);
				if (((List)obj).size() > index) {
					return((List) obj).get(index);
				} else {
					throw new NoSuchFieldException("Array index " + index + " out of bounds (" +((List)obj).size() + ")!");
				}
			} catch (Exception e) {
				return null;
			}
		} else {

		}
		throw new NoSuchFieldException("Class " + c.getName() + " does not have field " + key + " or methods " + key + "(), get" + u_key + "()!");
	}

	public static <T> T getInstance(Class<T> className, Object... parameters) {
		try {
			Class<?>[] classes = new Class<?>[parameters.length];
			for (int i = 0; i < parameters.length; i++) {
				classes[i] = parameters[i].getClass();
			}
			Constructor<T> c = className.getDeclaredConstructor(classes);
			return c.newInstance(parameters);
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (Exception e) {
			throw new IllegalArgumentException("Cannot create fileWriter:", e);
		}
	}
}
